package BNS.plumbing;

import java.io.IOException;
import java.io.LineNumberReader;
import java.util.Enumeration;
import java.util.Hashtable;

import BNS.graphs.Node;


/** This class is used to re-view a BNS game.
	It overrides the Manager's time model and implements the simulated time model.
	It needs a file of events to use for simulating player actions.
	
	Its client<-->node assignment policy copies the specific assignments used on
	previous occasions (which is useful for making movies of past games).
	The information for this assignment is taken from the 'clid' line in the gameLog.
	*/
public class SimuDriver extends Manager implements Runnable {
	
	protected BNSGameController game;
	protected MovieHost host;
	protected Hashtable<String,ClientLiaison> clientTable;
	protected LineNumberReader records;
		
	/** Creates a driver to supply events to a game.
		@param h the SessionHost that is running
		@param g the game about to be played
		@param r gives access to the file of events to be used
		*/
	public SimuDriver(MovieHost h, BNSGameController g, LineNumberReader r) 
						throws java.rmi.RemoteException {
		System.err.println("Manager created");
		host= h;
		game= g;
		timeLimit= game.getTimeLimit()*1000;
		clock= new Timing();
		records= r;
		System.err.println("SimuDriver created");
		try {
//			registerRobots(g.getSize());
			setupIDs(g.getSize());
			//System.err.println("ready to simulate game "+ game.getInfo());
			host.sessionLog.println("ready to simulate game "+ game.getInfo());
			//host.sessionLog.println("saving events in "+ Library.getGameLogName());
			}
		catch(java.rmi.RemoteException e) { 
			System.err.println("can't register! Why?");
			System.err.println(e); }
		catch(IOException e) { 
			System.err.println("trouble reading eventsFile");
			System.err.println(e); }
		host.supervisor.repaint();
		}
	
/*	private void registerRobots(int gameSize) throws java.rmi.RemoteException {
		for (int playerNum=1001; playerNum<=1000+gameSize; playerNum++) 
//			host.register(String.format("Robe Ott%03d", playerNum));
			host.register(playerNum);
			}
*/	
	private void setupIDs(int gameSize) throws IOException {
		String line;
		clientTable= new Hashtable<String,ClientLiaison>();
		records.reset();
		while ((line=records.readLine())!=null && !line.startsWith("clid")) 
			//System.err.println(":: "+line)
			;
		if (line==null) throw new IOException("missing clid line");
		
		String[] tokens= line.split("=");
		for (int i=0; i<tokens.length; i++)	{
		  System.err.print("  "+tokens[i]);
		  tokens[i]=tokens[i].substring(tokens[i].length()-4);
      System.err.print("  "+tokens[i]);
		}
		System.err.println(" <--tokens"); /**/
		
		for (int idx=0; idx<gameSize; idx++) {
			Node node= game.grpk.graph.getNode(idx);
			int clientID= Integer.parseInt(tokens[idx]);
//			ClientLiaison client= host.clients.elementAt(clientID-1001);
			ClientLiaison client= host.register(clientID);
			assert(client.clientID==clientID);
			clientTable.put(node.lbl,client);
			//System.err.println("clid ==> "+client.clientID +" new client");
			}
		}
	
	/** Checks to see if there are enough clients registered to play the current game.
		(TODO: See if this method is out of date; it had overrides once.) */
	public boolean ready(int required) {
		return required <= clientTable.size();
		}
	
	/** obtains a ClientLiaison (representing a Client, representing a player)
		to operate a given node.
		Which client is chosen for each node is specified by the CLID line in eventsFile.
		*/
	public ClientLiaison assign(Node node) { 
		ClientLiaison cl= clientTable.get(node.lbl); 
		assert (cl!=null);
		return cl;
		}
	
	public void run() {
		System.err.println("starting SimuDriver");
		String speed= System.getProperty("SPEED");
		if (speed!=null) game.clock.setSpeed(Float.parseFloat(speed));
		String line= "nothing read yet";
		String[] tokens;
		try {
			records.reset();
			while ((line=records.readLine())!=null) {
				System.err.println("ahoy! "+line);
				tokens= line.split(" ");
				if(tokens[0].charAt(0)<'0' || tokens[0].charAt(0)>'9') continue;
				//if (!tokens[0].equals("move")) continue;
				ClientLiaison client= clientTable.get(tokens[2]); // TODO: why use Node?
				long nextEvent= Long.parseLong(tokens[0]);
				clock.waitUntil("simu eventDriver",nextEvent);
				game.interpret(nextEvent,client,tokens);
				game.monitor.repaint(); 
				}
			System.err.println("eventsFile exhausted");
			records.close();
			// TODO: last event in records file may be after the time limit; should check for that.
			clock.waitUntil("timeout", timeLimit);
			game.finished("time limit expired", timeLimit);
			game.monitor.repaint(); 
			// TODO: How to dispose of fake clients and still see their earnings in sessionLog?
			}
		catch (IOException e) { 
			System.err.println("line="+line);
			e.printStackTrace(); }
		}
	
	public void bulletin(String message) {
		System.err.println("bulletin: "+ message);
		}
	
	/** Shuts down a game by reclaiming all its clients and directing them to waitgame instead.
		Also, this method records the payoff of each player in sessionLog. 
		*/
	public void closeGame() {
		synchronized(host.clients) {// switch everyone back to a wait state
			host.waitgame.clear();	// TODO: rethink this!
//			for (int clientNum= 0; clientNum<host.clients.size(); clientNum++) {
			for (ClientLiaison client : host.clients) {
//				ClientLiaison client= host.clients.elementAt(clientNum);
				if (client==null || client.game!=game) continue;
				synchronized(client) {
					client.adoptGame(host.waitgame,null);
					}
				}
			}
		host.supervisor.repaint();
		host.waitgame.add(" game "+ game.gameValue()+"\n");
		host.waitgame.add(game.getTitle()+ " game over.\n");
		host.sessionLog.println("--------------------------------------------------------------");
		// TODO: wait until all personas switched
		game= null;
		host.gameOver();	// TODO: fix to handle multiple games
		}
	
	public boolean assignClients(BNSGameController gameController, Enumeration<Node> nodes) {
		//game.gameLog.println("assignClients");
		assert (game==gameController);	// TODO: fix for running multiple games
		//int gameSize= gameController.getSize();
		//System.err.println("loading game "+ game.getInfo());
		// warning: the BNSGameControllers call this method while running on Event Dispatch Thread.
		// TODO: If this method evolves to be quite slow (e.g. it requires human involvement), 
		// then the thread must be released and the work accomplished on a different thread.
		synchronized(host.clients) {
			if (!ready(game.getSize())) return false;	// TODO: fix for running multiple games
			game.gameLog.print("clid");
			while (nodes.hasMoreElements()) {
				Node node= nodes.nextElement();
				ClientLiaison client= assign(node);
				synchronized(client) {
					client.adoptGame(game, node);
					client.sendMessage("loading "+ game.getTitle());
					game.gameLog.print(" "+client.clientID);
					}
				System.err.println("client "+client.playerName+" has node "+node.lbl);
				}
			game.gameLog.println();
			}
		host.sessionLog.println("loaded game "+ gameController.getInfo());
		host.sessionLog.println("saving events in "+ Library.getGameLogName());
		host.supervisor.repaint();
		return true;
		}
	
	}
